 /*******************************************************************************
  * Copyright (c) 2005, 2006 IBM Corporation and others.
  * All rights reserved. This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License v1.0
  * which accompanies this distribution, and is available at
  * http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors:
  * IBM Corporation - initial API and implementation
  *******************************************************************************/

 package org.eclipse.ui.internal.services;

 import java.util.ArrayList ;
 import java.util.Collection ;
 import java.util.List ;

 import org.eclipse.core.commands.Command;
 import org.eclipse.core.commands.IParameter;
 import org.eclipse.core.commands.Parameterization;
 import org.eclipse.core.commands.ParameterizedCommand;
 import org.eclipse.core.commands.common.NotDefinedException;
 import org.eclipse.core.runtime.IStatus;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.jface.util.IPropertyChangeListener;
 import org.eclipse.jface.util.PropertyChangeEvent;
 import org.eclipse.ui.IMemento;
 import org.eclipse.ui.commands.ICommandService;
 import org.eclipse.ui.internal.WorkbenchPlugin;
 import org.eclipse.ui.internal.util.Util;

 /**
  * <p>
  * A manager for items parsed from the preference store. This attaches a
  * listener to the registry after the first read, and monitors the preference
  * for changes from that point on. When {@link #dispose()} is called, the
  * listener is detached.
  * </p>
  * <p>
  * This class is only intended for internal use within the
  * <code>org.eclipse.ui.workbench</code> plug-in.
  * </p>
  *
  * @since 3.2
  */
 public abstract class PreferencePersistence extends RegistryPersistence {

     /**
      * Inserts the given element into the indexed two-dimensional array in the
      * array at the index. The array is grown as necessary.
      *
      * @param elementToAdd
      * The element to add to the indexed array; may be
      * <code>null</code>
      * @param indexedArray
      * The two-dimensional array that is indexed by element type;
      * must not be <code>null</code>.
      * @param index
      * The index at which the element should be added; must be a
      * valid index.
      * @param currentCount
      * The current number of items in the array at the index.
      */
     protected static final void addElementToIndexedArray(
             final IMemento elementToAdd, final IMemento[][] indexedArray,
             final int index, final int currentCount) {
         final IMemento[] elements;
         if (currentCount == 0) {
             elements = new IMemento[1];
             indexedArray[index] = elements;
         } else {
             if (currentCount >= indexedArray[index].length) {
                 final IMemento[] copy = new IMemento[indexedArray[index].length * 2];
                 System.arraycopy(indexedArray[index], 0, copy, 0, currentCount);
                 elements = copy;
                 indexedArray[index] = elements;
             } else {
                 elements = indexedArray[index];
             }
         }
         elements[currentCount] = elementToAdd;
     }

     /**
      * Adds a warning to be logged at some later point in time.
      *
      * @param warningsToLog
      * The collection of warnings to be logged; must not be
      * <code>null</code>.
      * @param message
      * The mesaage to log; must not be <code>null</code>.
      */
     protected static final void addWarning(final List warningsToLog,
             final String message) {
         addWarning(warningsToLog, message, null, null, null);
     }

     /**
      * Adds a warning to be logged at some later point in time. This logs the
      * identifier of the item.
      *
      * @param warningsToLog
      * The collection of warnings to be logged; must not be
      * <code>null</code>.
      * @param message
      * The mesaage to log; must not be <code>null</code>.
      * @param id
      * The identifier of the item for which a warning is being
      * logged; may be <code>null</code>.
      */
     protected static final void addWarning(final List warningsToLog,
             final String message, final String id) {
         addWarning(warningsToLog, message, id, null, null);
     }

     /**
      * Adds a warning to be logged at some later point in time. This logs the
      * identifier of the item, as well as an extra attribute.
      *
      * @param warningsToLog
      * The collection of warnings to be logged; must not be
      * <code>null</code>.
      * @param message
      * The mesaage to log; must not be <code>null</code>.
      * @param id
      * The identifier of the item for which a warning is being
      * logged; may be <code>null</code>.
      * @param extraAttributeName
      * The name of extra attribute to be logged; may be
      * <code>null</code>.
      * @param extraAttributeValue
      * The value of the extra attribute to be logged; may be
      * <code>null</code>.
      */
     protected static final void addWarning(final List warningsToLog,
             final String message, final String id,
             final String extraAttributeName, final String extraAttributeValue) {
         String statusMessage = message;
         if (id != null) {
             statusMessage = statusMessage + ": id='" + id + '\''; //$NON-NLS-1$
 }
         if (extraAttributeName != null) {
             if (id != null) {
                 statusMessage = statusMessage + ',';
             } else {
                 statusMessage = statusMessage + ':';
             }
             statusMessage = statusMessage + ' ' + extraAttributeName + "='" //$NON-NLS-1$
 + extraAttributeValue + '\'';
         }

         final IStatus status = new Status(IStatus.WARNING,
                 WorkbenchPlugin.PI_WORKBENCH, 0, statusMessage, null);
         warningsToLog.add(status);
     }

     /**
      * Reads a boolean attribute from a memnto.
      *
      * @param memento
      * The memento from which to read the attribute; must not be
      * <code>null</code>.
      * @param attribute
      * The attribute to read; must not be <code>null</code>.
      * @param defaultValue
      * The default boolean value.
      * @return The attribute's value; may be <code>null</code> if none.
      */
     protected static final boolean readBoolean(final IMemento memento,
             final String attribute, final boolean defaultValue) {
         final String value = memento.getString(attribute);
         if (value == null) {
             return defaultValue;
         }

         if (defaultValue) {
             return !value.equalsIgnoreCase("false"); //$NON-NLS-1$
 }

         return !value.equalsIgnoreCase("true"); //$NON-NLS-1$
 }

     /**
      * Reads an optional attribute from a memento. This converts zero-length
      * strings into <code>null</code>.
      *
      * @param memento
      * The memento from which to read the attribute; must not be
      * <code>null</code>.
      * @param attribute
      * The attribute to read; must not be <code>null</code>.
      * @return The attribute's value; may be <code>null</code> if none.
      */
     protected static final String readOptional(final IMemento memento,
             final String attribute) {
         String value = memento.getString(attribute);
         if ((value != null) && (value.length() == 0)) {
             value = null;
         }

         return value;
     }

     /**
      * Reads the parameterized command from a parent memento. This is used to
      * read the parameter sub-elements from a key element, as well as the
      * command id. Each parameter is guaranteed to be valid. If invalid
      * parameters are found, then a warning status will be appended to the
      * <code>warningsToLog</code> list. The command id is required, or a
      * warning will be logged.
      *
      * @param memento
      * The memento from which the parameters should be read; must not
      * be <code>null</code>.
      * @param commandService
      * The service providing commands for the workbench; must not be
      * <code>null</code>.
      * @param warningsToLog
      * The list of warnings found during parsing. Warnings found will
      * parsing the parameters will be appended to this list. This
      * value must not be <code>null</code>.
      * @param message
      * The message to print if the command identifier is not present;
      * must not be <code>null</code>.
      * @return The array of parameters found for this configuration element;
      * <code>null</code> if none can be found.
      */
     protected static final ParameterizedCommand readParameterizedCommand(
             final IMemento memento, final ICommandService commandService,
             final List warningsToLog, final String message, final String id) {
         final String commandId = readRequired(memento, ATT_COMMAND_ID,
                 warningsToLog, message, id);
         if (commandId == null) {
             return null;
         }

         final Command command = commandService.getCommand(commandId);
         final ParameterizedCommand parameterizedCommand = readParameters(
                 memento, warningsToLog, command);

         return parameterizedCommand;
     }

     /**
      * Reads the parameters from a parent memento. This is used to read the
      * parameter sub-elements from a key element. Each parameter is guaranteed
      * to be valid. If invalid parameters are found, then a warning status will
      * be appended to the <code>warningsToLog</code> list.
      *
      * @param memento
      * The memento from which the parameters should be read; must not
      * be <code>null</code>.
      * @param warningsToLog
      * The list of warnings found during parsing. Warnings found will
      * parsing the parameters will be appended to this list. This
      * value must not be <code>null</code>.
      * @param command
      * The command around which the parameterization should be
      * created; must not be <code>null</code>.
      * @return The array of parameters found for this memento; <code>null</code>
      * if none can be found.
      */
     protected static final ParameterizedCommand readParameters(
             final IMemento memento, final List warningsToLog,
             final Command command) {
         final IMemento[] parameterMementos = memento
                 .getChildren(TAG_PARAMETER);
         if ((parameterMementos == null) || (parameterMementos.length == 0)) {
             return new ParameterizedCommand(command, null);
         }

         final Collection parameters = new ArrayList ();
         for (int i = 0; i < parameterMementos.length; i++) {
             final IMemento parameterMemento = parameterMementos[i];

             // Read out the id.
 final String id = parameterMemento.getString(ATT_ID);
             if ((id == null) || (id.length() == 0)) {
                 // The name should never be null. This is invalid.
 addWarning(warningsToLog, "Parameters need a name"); //$NON-NLS-1$
 continue;
             }

             // Find the parameter on the command.
 IParameter parameter = null;
             try {
                 final IParameter[] commandParameters = command.getParameters();
                 if (parameters != null) {
                     for (int j = 0; j < commandParameters.length; j++) {
                         final IParameter currentParameter = commandParameters[j];
                         if (Util.equals(currentParameter.getId(), id)) {
                             parameter = currentParameter;
                             break;
                         }
                     }

                 }
             } catch (final NotDefinedException e) {
                 // This should not happen.
 }
             if (parameter == null) {
                 // The name should never be null. This is invalid.
 addWarning(warningsToLog,
                         "Could not find a matching parameter", id); //$NON-NLS-1$
 continue;
             }

             // Read out the value.
 final String value = parameterMemento.getString(ATT_VALUE);
             if ((value == null) || (value.length() == 0)) {
                 // The name should never be null. This is invalid.
 addWarning(warningsToLog, "Parameters need a value", id); //$NON-NLS-1$
 continue;
             }

             parameters.add(new Parameterization(parameter, value));
         }

         if (parameters.isEmpty()) {
             return new ParameterizedCommand(command, null);
         }

         return new ParameterizedCommand(command,
                 (Parameterization[]) parameters
                         .toArray(new Parameterization[parameters.size()]));
     }

     /**
      * Reads a required attribute from the memento.
      *
      * @param memento
      * The memento from which to read; must not be <code>null</code>.
      * @param attribute
      * The attribute to read; must not be <code>null</code>.
      * @param warningsToLog
      * The list of warnings; must not be <code>null</code>.
      * @param message
      * The warning message to use if the attribute is missing; must
      * not be <code>null</code>.
      * @return The required attribute; may be <code>null</code> if missing.
      */
     protected static final String readRequired(final IMemento memento,
             final String attribute, final List warningsToLog,
             final String message) {
         return readRequired(memento, attribute, warningsToLog, message, null);
     }

     /**
      * Reads a required attribute from the memento. This logs the identifier of
      * the item if this required element cannot be found.
      *
      * @param memento
      * The memento from which to read; must not be <code>null</code>.
      * @param attribute
      * The attribute to read; must not be <code>null</code>.
      * @param warningsToLog
      * The list of warnings; must not be <code>null</code>.
      * @param message
      * The warning message to use if the attribute is missing; must
      * not be <code>null</code>.
      * @param id
      * The identifier of the element for which this is a required
      * attribute; may be <code>null</code>.
      * @return The required attribute; may be <code>null</code> if missing.
      */
     protected static final String readRequired(final IMemento memento,
             final String attribute, final List warningsToLog,
             final String message, final String id) {
         final String value = memento.getString(attribute);
         if ((value == null) || (value.length() == 0)) {
             addWarning(warningsToLog, message, id);
             return null;
         }

         return value;
     }

     /**
      * Whether the preference and registry change listeners have been attached
      * yet.
      */
     protected boolean preferenceListenerAttached = false;

     /**
      * The registry change listener for this class.
      */
     private final IPropertyChangeListener preferenceChangeListener;

     /**
      * Detaches the preference change listener from the registry.
      */
     public final void dispose() {
         super.dispose();

         final IPreferenceStore store = WorkbenchPlugin.getDefault()
                 .getPreferenceStore();
         store.removePropertyChangeListener(preferenceChangeListener);
     }

     /**
      * Checks whether the preference change could affect this persistence class.
      *
      * @param event
      * The event indicating the preference change; must not be
      * <code>null</code>.
      * @return <code>true</code> if the persistence instance is affected by
      * this change; <code>false</code> otherwise.
      */
     protected abstract boolean isChangeImportant(final PropertyChangeEvent event);

     /**
      * Reads the various elements from the registry. Subclasses should extend,
      * but must not override.
      */
     protected void read() {
         super.read();

         if (!preferenceListenerAttached) {
             final IPreferenceStore store = WorkbenchPlugin.getDefault()
                     .getPreferenceStore();
             store.addPropertyChangeListener(preferenceChangeListener);
         }
     }

     /**
      * Constructs a new instance of {@link PreferencePersistence}. A preference
      * change listener is created.
      */
     protected PreferencePersistence() {
         super();

         preferenceChangeListener = new IPropertyChangeListener() {
             public final void propertyChange(final PropertyChangeEvent event) {
                 if (isChangeImportant(event)) {
                     read();
                 }
             }
         };
     }
 }

